home *** CD-ROM | disk | FTP | other *** search
/ Aminet 1 (Walnut Creek) / Aminet - June 1993 [Walnut Creek].iso / aminet / util / gnu / textutl3.lha / textutils-1.3 / src / tac.c < prev    next >
C/C++ Source or Header  |  1992-06-29  |  16KB  |  617 lines

  1. /* tac - concatenate and print files in reverse
  2.    Copyright (C) 1988, 1989, 1990, 1991 Free Software Foundation, Inc.
  3.  
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 2, or (at your option)
  7.    any later version.
  8.  
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18. /* Written by Jay Lepreau (lepreau@cs.utah.edu).
  19.    GNU enhancements by David MacKenzie (djm@ai.mit.edu). */
  20.  
  21. /* Copy each FILE, or the standard input if none are given or when a
  22.    FILE name of "-" is encountered, to the standard output with the
  23.    order of the records reversed.  The records are separated by
  24.    instances of a string, or a newline if none is given.  By default, the
  25.    separator string is attached to the end of the record that it
  26.    follows in the file.
  27.  
  28.    Options:
  29.    -b, --before            The separator is attached to the beginning
  30.                 of the record that it precedes in the file.
  31.    -r, --regex            The separator is a regular expression.
  32.    -s, --separator=separator    Use SEPARATOR as the record separator.
  33.  
  34.    To reverse a file byte by byte, use (in bash, ksh, or sh):
  35. tac -r -s '.\|
  36. ' file */
  37.  
  38. #include <stdio.h>
  39. #include <getopt.h>
  40. #include <sys/types.h>
  41. #include <signal.h>
  42. #include <regex.h>
  43. #include "system.h"
  44.  
  45. #ifndef STDC_HEADERS
  46. char *malloc ();
  47. char *realloc ();
  48. #endif
  49.  
  50. /* The number of bytes per atomic read. */
  51. #define INITIAL_READSIZE 8192
  52.  
  53. /* The number of bytes per atomic write. */
  54. #define WRITESIZE 8192
  55.  
  56. char *mktemp ();
  57.  
  58. SIGTYPE cleanup ();
  59. int tac ();
  60. int tac_file ();
  61. int tac_stdin ();
  62. char *xmalloc ();
  63. char *xrealloc ();
  64. void output ();
  65. void error ();
  66. void save_stdin ();
  67. void xwrite ();
  68.  
  69. /* The name this program was run with. */
  70. char *program_name;
  71.  
  72. /* The string that separates the records of the file. */
  73. char *separator;
  74.  
  75. /* If nonzero, print `separator' along with the record preceding it
  76.    in the file; otherwise with the record following it. */
  77. int separator_ends_record;
  78.  
  79. /* 0 if `separator' is to be matched as a regular expression;
  80.    otherwise, the length of `separator', used as a sentinel to
  81.    stop the search. */
  82. int sentinel_length;
  83.  
  84. /* The length of a match with `separator'.  If `sentinel_length' is 0,
  85.    `match_length' is computed every time a match succeeds;
  86.    otherwise, it is simply the length of `separator'. */
  87. int match_length;
  88.  
  89. /* The input buffer. */
  90. char *buffer;
  91.  
  92. /* The number of bytes to read at once into `buffer'. */
  93. unsigned read_size;
  94.  
  95. /* The size of `buffer'.  This is read_size * 2 + sentinel_length + 2.
  96.    The extra 2 bytes allow `past_end' to have a value beyond the
  97.    end of `buffer' and `match_start' to run off the front of `buffer'. */
  98. unsigned buffer_size;
  99.  
  100. /* The compiled regular expression representing `separator'. */
  101. static struct re_pattern_buffer compiled_separator;
  102.  
  103. struct option longopts[] =
  104. {
  105.   {"before", 0, &separator_ends_record, 0},
  106.   {"regex", 0, &sentinel_length, 0},
  107.   {"separator", 1, NULL, 's'},
  108.   {NULL, 0, NULL, 0}
  109. };
  110.  
  111. void
  112. main (argc, argv)
  113.      int argc;
  114.      char **argv;
  115. {
  116.   char *error_message;        /* Return value from re_compile_pattern. */
  117.   int optc, errors;
  118.   int have_read_stdin = 0;
  119.  
  120.   program_name = argv[0];
  121.   errors = 0;
  122.   separator = "\n";
  123.   sentinel_length = 1;
  124.   separator_ends_record = 1;
  125.  
  126.   while ((optc = getopt_long (argc, argv, "brs:", longopts, (int *) 0))
  127.      != EOF)
  128.     {
  129.       switch (optc)
  130.     {
  131.     case 0:
  132.       break;
  133.     case 'b':
  134.       separator_ends_record = 0;
  135.       break;
  136.     case 'r':
  137.       sentinel_length = 0;
  138.       break;
  139.     case 's':
  140.       separator = optarg;
  141.       if (*separator == 0)
  142.         error (1, 0, "separator cannot be empty");
  143.       break;
  144.     default:
  145.       fprintf (stderr, "\
  146. Usage: %s [-br] [-s separator] [--before] [--regex] [--separator=separator]\n\
  147.        [file...]\n",
  148.            program_name);
  149.       exit (1);
  150.     }
  151.     }
  152.  
  153.   if (sentinel_length == 0)
  154.     {
  155.       compiled_separator.allocated = 100;
  156.       compiled_separator.buffer = (unsigned char *)
  157.     xmalloc (compiled_separator.allocated);
  158.       compiled_separator.fastmap = xmalloc (256);
  159.       compiled_separator.translate = 0;
  160.       error_message = re_compile_pattern (separator, strlen (separator),
  161.                       &compiled_separator);
  162.       if (error_message)
  163.     error (1, 0, "%s", error_message);
  164.     }
  165.   else
  166.     match_length = sentinel_length = strlen (separator);
  167.  
  168.   read_size = INITIAL_READSIZE;
  169.   /* A precaution that will probably never be needed. */
  170.   while (sentinel_length * 2 >= read_size)
  171.     read_size *= 2;
  172.   buffer_size = read_size * 2 + sentinel_length + 2;
  173.   buffer = xmalloc (buffer_size);
  174.   if (sentinel_length)
  175.     {
  176.       strcpy (buffer, separator);
  177.       buffer += sentinel_length;
  178.     }
  179.   else
  180.     ++buffer;
  181.  
  182.   if (optind == argc)
  183.     {
  184.       have_read_stdin = 1;
  185.       errors = tac_stdin ();
  186.     }
  187.   else
  188.     for (; optind < argc; ++optind)
  189.       {
  190.     if (strcmp (argv[optind], "-") == 0)
  191.       {
  192.         have_read_stdin = 1;
  193.         errors |= tac_stdin ();
  194.       }
  195.     else
  196.       errors |= tac_file (argv[optind]);
  197.       }
  198.  
  199.   /* Flush the output buffer. */
  200.   output ((char *) NULL, (char *) NULL);
  201.  
  202.   if (have_read_stdin && close (0) < 0)
  203.     error (1, errno, "-");
  204.   if (close (1) < 0)
  205.     error (1, errno, "write error");
  206.   exit (errors);
  207. }
  208.  
  209. /* The name of a temporary file containing a copy of pipe input. */
  210. char *tempfile;
  211.  
  212. /* Print the standard input in reverse, saving it to temporary
  213.    file `tempfile' first if it is a pipe.
  214.    Return 0 if ok, 1 if an error occurs. */
  215.  
  216. int
  217. tac_stdin ()
  218. {
  219.   /* Previous values of signal handlers. */
  220.   SIGTYPE (*sigint) (), (*sighup) (), (*sigterm) ();
  221.   int errors;
  222.   struct stat stats;
  223. #ifdef _POSIX_VERSION
  224.     struct sigaction oldact, newact;
  225. #endif                /* _POSIX_VERSION */
  226.  
  227.   /* No tempfile is needed for "tac < file".
  228.      Use fstat instead of checking for errno == ESPIPE because
  229.      lseek doesn't work on some special files but doesn't return an
  230.      error, either. */
  231.   if (fstat (0, &stats))
  232.     {
  233.       error (0, errno, "standard input");
  234.       return 1;
  235.     }
  236.   if (S_ISREG (stats.st_mode))
  237.     return tac (0, "standard input");
  238.  
  239. #ifdef _POSIX_VERSION
  240.   newact.sa_handler = cleanup;
  241.   sigemptyset (&newact.sa_mask);
  242.   newact.sa_flags = 0;
  243.  
  244.   sigaction (SIGINT, NULL, &oldact);
  245.   sigint = oldact.sa_handler;
  246.   if (sigint != SIG_IGN)
  247.     sigaction (SIGINT, &newact, NULL);
  248.  
  249.   sigaction (SIGHUP, NULL, &oldact);
  250.   sighup = oldact.sa_handler;
  251.   if (sighup != SIG_IGN)
  252.     sigaction (SIGHUP, &newact, NULL);
  253.  
  254.   sigaction (SIGTERM, NULL, &oldact);
  255.   sigterm = oldact.sa_handler;
  256.   if (sigterm != SIG_IGN)
  257.     sigaction (SIGTERM, &newact, NULL);
  258. #else                /* !_POSIX_VERSION */
  259.   sigint = signal (SIGINT, SIG_IGN);
  260.   if (sigint != SIG_IGN)
  261.     signal (SIGINT, cleanup);
  262.  
  263.   sighup = signal (SIGHUP, SIG_IGN);
  264.   if (sighup != SIG_IGN)
  265.     signal (SIGHUP, cleanup);
  266.  
  267.   sigterm = signal (SIGTERM, SIG_IGN);
  268.   if (sigterm != SIG_IGN)
  269.     signal (SIGTERM, cleanup);
  270. #endif                /* _POSIX_VERSION */
  271.  
  272.   save_stdin ();
  273.  
  274.   errors = tac_file (tempfile);
  275.  
  276.   unlink (tempfile);
  277.  
  278. #ifdef _POSIX_VERSION
  279.   newact.sa_handler = sigint;
  280.   sigaction (SIGINT, &newact, NULL);
  281.   newact.sa_handler = sighup;
  282.   sigaction (SIGHUP, &newact, NULL);
  283.   newact.sa_handler = sigterm;
  284.   sigaction (SIGTERM, &newact, NULL);
  285. #else                /* !_POSIX_VERSION */
  286.   signal (SIGINT, sigint);
  287.   signal (SIGHUP, sighup);
  288.   signal (SIGTERM, sigterm);
  289. #endif                /* _POSIX_VERSION */
  290.  
  291.   return errors;
  292. }
  293.  
  294. /* Make a copy of the standard input in `tempfile'. */
  295.  
  296. void
  297. save_stdin ()
  298. {
  299.   static char *template = NULL;
  300.   static char *tempdir;
  301.   int fd;
  302.   int bytes_read;
  303.  
  304.   if (template == NULL)
  305.     {
  306.       tempdir = getenv ("TMPDIR");
  307.       if (tempdir == NULL)
  308.     tempdir = "/tmp";
  309.       template = xmalloc (strlen (tempdir) + 11);
  310.     }
  311.   sprintf (template, "%s/tacXXXXXX", tempdir);
  312.   tempfile = mktemp (template);
  313.  
  314.   fd = creat (tempfile, 0600);
  315.   if (fd == -1)
  316.     {
  317.       error (0, errno, "%s", tempfile);
  318.       cleanup ();
  319.     }
  320.   while ((bytes_read = read (0, buffer, read_size)) > 0)
  321.     if (write (fd, buffer, bytes_read) != bytes_read)
  322.       {
  323.     error (0, errno, "%s", tempfile);
  324.     cleanup ();
  325.       }
  326.   if (close (fd) < 0)
  327.     {
  328.       error (0, errno, "%s", tempfile);
  329.       cleanup ();
  330.     }
  331.   if (bytes_read == -1)
  332.     {
  333.       error (0, errno, "read error");
  334.       cleanup ();
  335.     }
  336. }
  337.  
  338. /* Print FILE in reverse.
  339.    Return 0 if ok, 1 if an error occurs. */
  340.  
  341. int
  342. tac_file (file)
  343.      char *file;
  344. {
  345.   int fd, errors;
  346.  
  347.   fd = open (file, 0);
  348.   if (fd == -1)
  349.     {
  350.       error (0, errno, "%s", file);
  351.       return 1;
  352.     }
  353.   errors = tac (fd, file);
  354.   if (close (fd) < 0)
  355.     {
  356.       error (0, errno, "%s", file);
  357.       return 1;
  358.     }
  359.   return errors;
  360. }
  361.  
  362. /* Print in reverse the file open on descriptor FD for reading FILE.
  363.    Return 0 if ok, 1 if an error occurs. */
  364.  
  365. int
  366. tac (fd, file)
  367.      int fd;
  368.      char *file;
  369. {
  370.   /* Pointer to the location in `buffer' where the search for
  371.      the next separator will begin. */
  372.   char *match_start;
  373.   /* Pointer to one past the rightmost character in `buffer' that
  374.      has not been printed yet. */
  375.   char *past_end;
  376.   unsigned saved_record_size;    /* Length of the record growing in `buffer'. */
  377.   off_t file_pos;        /* Offset in the file of the next read. */
  378.   /* Nonzero if `output' has not been called yet for any file.
  379.      Only used when the separator is attached to the preceding record. */
  380.   int first_time = 1;
  381.   char first_char = *separator;    /* Speed optimization, non-regexp. */
  382.   char *separator1 = separator + 1; /* Speed optimization, non-regexp. */
  383.   int match_length1 = match_length - 1; /* Speed optimization, non-regexp. */
  384.   struct re_registers regs;
  385.  
  386.   /* Find the size of the input file. */
  387.   file_pos = lseek (fd, (off_t) 0, SEEK_END);
  388.   if (file_pos < 1)
  389.     return 0;            /* It's an empty file. */
  390.  
  391.   /* Arrange for the first read to lop off enough to leave the rest of the
  392.      file a multiple of `read_size'.  Since `read_size' can change, this may
  393.      not always hold during the program run, but since it usually will, leave
  394.      it here for i/o efficiency (page/sector boundaries and all that).
  395.      Note: the efficiency gain has not been verified. */
  396.   saved_record_size = file_pos % read_size;
  397.   if (saved_record_size == 0)
  398.     saved_record_size = read_size;
  399.   file_pos -= saved_record_size;
  400.   /* `file_pos' now points to the start of the last (probably partial) block
  401.      in the input file. */
  402.  
  403.   lseek (fd, file_pos, SEEK_SET);
  404.   if (read (fd, buffer, saved_record_size) != saved_record_size)
  405.     {
  406.       error (0, 1, "%s", file);
  407.       return 1;
  408.     }
  409.  
  410.   match_start = past_end = buffer + saved_record_size;
  411.   /* For non-regexp search, move past impossible positions for a match. */
  412.   if (sentinel_length)
  413.     match_start -= match_length1;
  414.  
  415.   for (;;)
  416.     {
  417.       /* Search backward from `match_start' - 1 to `buffer' for a match
  418.      with `separator'; for speed, use strncmp if `separator' contains no
  419.      metacharacters.
  420.      If the match succeeds, set `match_start' to point to the start of
  421.      the match and `match_length' to the length of the match.
  422.      Otherwise, make `match_start' < `buffer'. */
  423.       if (sentinel_length == 0)
  424.     {
  425.       int i = match_start - buffer;
  426.       int ret;
  427.  
  428.       ret = re_search (&compiled_separator, buffer, i, i - 1, -i, ®s);
  429.       if (ret == -1)
  430.         match_start = buffer - 1;
  431.       else if (ret == -2)
  432.         {
  433.           error (0, 0, "error in regular expression search");
  434.           cleanup ();
  435.         }
  436.       else
  437.         {
  438.           match_start = buffer + regs.start[0];
  439.           match_length = regs.end[0] - regs.start[0];
  440.         }
  441.     }
  442.       else
  443.     {
  444.       /* `match_length' is constant for non-regexp boundaries. */
  445.       while (*--match_start != first_char
  446.          || (match_length1 && strncmp (match_start + 1, separator1,
  447.                            match_length1)))
  448.         /* Do nothing. */ ;
  449.     }
  450.  
  451.       /* Check whether we backed off the front of `buffer' without finding
  452.          a match for `separator'. */
  453.       if (match_start < buffer)
  454.     {
  455.       if (file_pos == 0)
  456.         {
  457.           /* Hit the beginning of the file; print the remaining record. */
  458.           output (buffer, past_end);
  459.           return 0;
  460.         }
  461.  
  462.       saved_record_size = past_end - buffer;
  463.       if (saved_record_size > read_size)
  464.         {
  465.           /* `buffer_size' is about twice `read_size', so since
  466.          we want to read in another `read_size' bytes before
  467.          the data already in `buffer', we need to increase
  468.          `buffer_size'. */
  469.           char *newbuffer;
  470.           int offset = sentinel_length ? sentinel_length : 1;
  471.  
  472.           read_size *= 2;
  473.           buffer_size = read_size * 2 + sentinel_length + 2;
  474.           newbuffer = xrealloc (buffer - offset, buffer_size) + offset;
  475.           /* Adjust the pointers for the new buffer location.  */
  476.           match_start += newbuffer - buffer;
  477.           past_end += newbuffer - buffer;
  478.           buffer = newbuffer;
  479.         }
  480.  
  481.       /* Back up to the start of the next bufferfull of the file.  */
  482.       if (file_pos >= read_size)
  483.         file_pos -= read_size;
  484.       else
  485.         {
  486.           read_size = file_pos;
  487.           file_pos = 0;
  488.         }
  489.       lseek (fd, file_pos, SEEK_SET);
  490.  
  491.       /* Shift the pending record data right to make room for the new. */
  492.       bcopy (buffer, buffer + read_size, saved_record_size);
  493.       past_end = buffer + read_size + saved_record_size;
  494.       /* For non-regexp searches, avoid unneccessary scanning. */
  495.       if (sentinel_length)
  496.         match_start = buffer + read_size;
  497.       else
  498.         match_start = past_end;
  499.  
  500.       if (read (fd, buffer, read_size) != read_size)
  501.         {
  502.           error (0, errno, "%s", file);
  503.           return 1;
  504.         }
  505.     }
  506.       else
  507.     {
  508.       /* Found a match of `separator'. */
  509.       if (separator_ends_record)
  510.         {
  511.           char *match_end = match_start + match_length;
  512.  
  513.           /* If this match of `separator' isn't at the end of the
  514.              file, print the record. */
  515.           if (first_time == 0 || match_end != past_end)
  516.         output (match_end, past_end);
  517.           past_end = match_end;
  518.           first_time = 0;
  519.         }
  520.       else
  521.         {
  522.           output (match_start, past_end);
  523.           past_end = match_start;
  524.         }
  525.       match_start -= match_length - 1;
  526.     }
  527.     }
  528. }
  529.  
  530. /* Print the characters from START to PAST_END - 1.
  531.    If START is NULL, just flush the buffer. */
  532.  
  533. void
  534. output (start, past_end)
  535.      char *start;
  536.      char *past_end;
  537. {
  538.   static char buffer[WRITESIZE];
  539.   static int bytes_in_buffer = 0;
  540.   int bytes_to_add = past_end - start;
  541.   int bytes_available = WRITESIZE - bytes_in_buffer;
  542.  
  543.   if (start == 0)
  544.     {
  545.       xwrite (1, buffer, bytes_in_buffer);
  546.       bytes_in_buffer = 0;
  547.       return;
  548.     }
  549.   
  550.   /* Write out as many full buffers as possible. */
  551.   while (bytes_to_add >= bytes_available)
  552.     {
  553.       bcopy (start, buffer + bytes_in_buffer, bytes_available);
  554.       bytes_to_add -= bytes_available;
  555.       start += bytes_available;
  556.       xwrite (1, buffer, WRITESIZE);
  557.       bytes_in_buffer = 0;
  558.       bytes_available = WRITESIZE;
  559.     }
  560.  
  561.   bcopy (start, buffer + bytes_in_buffer, bytes_to_add);
  562.   bytes_in_buffer += bytes_to_add;
  563. }
  564.  
  565. SIGTYPE
  566. cleanup ()
  567. {
  568.   unlink (tempfile);
  569.   exit (1);
  570. }
  571.  
  572. void
  573. xwrite (desc, buffer, size)
  574.      int desc;
  575.      char *buffer;
  576.      int size;
  577. {
  578.   if (write (desc, buffer, size) != size)
  579.     {
  580.       error (0, errno, "write error");
  581.       cleanup ();
  582.     }
  583. }
  584.  
  585. /* Allocate N bytes of memory dynamically, with error checking.  */
  586.  
  587. char *
  588. xmalloc (n)
  589.      unsigned n;
  590. {
  591.   char *p;
  592.  
  593.   p = malloc (n);
  594.   if (p == 0)
  595.     {
  596.       error (0, 0, "virtual memory exhausted");
  597.       cleanup ();
  598.     }
  599.   return p;
  600. }
  601.  
  602. /* Change the size of memory area P to N bytes, with error checking. */
  603.  
  604. char *
  605. xrealloc (p, n)
  606.      char *p;
  607.      unsigned n;
  608. {
  609.   p = realloc (p, n);
  610.   if (p == 0)
  611.     {
  612.       error (0, 0, "virtual memory exhausted");
  613.       cleanup ();
  614.     }
  615.   return p;
  616. }
  617.